package com.knowledge.plus.java8;

import com.alibaba.fastjson.JSON;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Stream流：创建流 -> 中间操作 -> 终结操作
 * 注意：stream具有延迟执行特性，只有调用终端操作时，中间操作才会执行。不会影响原数据
 * 示例：https://blog.csdn.net/mu_wind/article/details/109516995
 * <p>
 * {@link CollectStream#forEach()}              遍历
 * {@link CollectStream#filter()}               过滤
 * {@link CollectStream#map()}                  将每个元素通过给定的映射函数转换为另一种类型
 * {@link CollectStream#flatMap()}              将每个元素通过给定的映射函数转换为Stream并将多个Stream合并为一个Stream
 * {@link CollectStream#distinct()}             去除重复元素
 * {@link CollectStream#sorted()}               对Stream中的元素进行排序，默认按照自然顺序进行排序
 * {@link CollectStream#sortByComparator()}     根据给定的Comparator对Stream中的元素进行排序
 * {@link CollectStream#limit()}                截取Stream中的前 maxSize 个元素
 * {@link CollectStream#skip()}                 跳过Stream中的前n个元素
 * {@link CollectStream#collect()}              将Stream中的元素收集到一个集合或数据结构中，并返回结果
 * {@link CollectStream#toArray()}              将Stream中的元素转换为数组
 * {@link CollectStream#anyMatch()}             判断Stream中是否存在满足给定条件的元素
 * {@link CollectStream#allMatch()}             判断Stream中的所有元素是否都满足给定条件
 * {@link CollectStream#noneMatch()}            判断Stream中是否不存在满足给定条件的元素
 * {@link CollectStream#findFirst()}            返回Stream中的第一个元素
 * {@link CollectStream#findAny()}              返回Stream中的任意一个元素
 * {@link CollectStream#reduce()}               对Stream中的数据按照你指定的计算方式计算出一个结果
 * <p>
 * {@link CollectStream#groupingBy()}           取集合中的某一字段作为Key，进行分组
 * {@link CollectStream#joining()}              取对象中的某个元素并连接它们，用逗号分隔
 * {@link CollectStream#partitioningBy()}       将流的元素分为两部分
 *
 * @author TPF
 * @since 2022/11/18
 */
public class CollectStream {

    List<Student> list;

    public CollectStream() {
        list = new ArrayList<>();
        list.add(Student.builder().name("张三").age(22).sex("男").build());
        list.add(Student.builder().name("张三").age(66).sex(" ").build());
        list.add(Student.builder().name("李四").age(34).sex(" ").build());
        list.add(Student.builder().name("王五").age(18).sex("女").build());
    }

    /**
     * 遍历：forEach(Consumer<? super T> action)
     */
    @Test
    public void forEach() {
        list.forEach(name -> System.out.print(name + "  "));
    }

    /**
     * 过滤：filter(Predicate<T> predicate)
     * 根据给定的条件过滤出符合条件的元素并返回一个新的 Stream
     */
    @Test
    public void filter() {
        list.stream()
                .filter(student -> StringUtils.isNotBlank(student.getSex()))
                .forEach(System.out::println);
    }

    /**
     * map(Function<T, R> mapper)
     * 将每个元素通过给定的映射函数转换为另一种类型，并返回一个新的Stream
     */
    @Test
    public void map() {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        List<Integer> nameLengths = names.stream()
                .map(String::length)
                .collect(Collectors.toList());
        System.out.println(nameLengths);
    }

    /**
     * flatMap(Function<T, Stream<R>> mapper)
     * 将每个元素通过给定的映射函数转换为Stream并将多个Stream合并为一个Stream
     */
    @Test
    public void flatMap() {
        List<List<Integer>> numbers = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5, 6));
        List<Integer> flattenedNumbers = numbers.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
        System.out.println(flattenedNumbers);
    }

    /**
     * distinct()
     * 去除重复元素，返回一个去重后的新Stream
     */
    @Test
    public void distinct() {
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5, 5);
        List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println(distinctNumbers);
    }

    /**
     * sorted()
     * 对Stream中的元素进行排序，默认按照自然顺序进行排序。
     */
    @Test
    public void sorted() {
        List<Integer> numbers = Arrays.asList(5, 2, 4, 1, 3);
        List<Integer> sortedNumbers = numbers.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println(sortedNumbers);
    }

    /**
     * sorted(Comparator<T> comparator)
     * 根据给定的Comparator对Stream中的元素进行排序
     */
    @Test
    public void sortByComparator() {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        List<String> sortedNames = names.stream()
                .sorted(Comparator.reverseOrder())  // 按字母降序排序 ["Charlie", "Bob", "Alice"]
                .collect(Collectors.toList());
        System.out.println(sortedNames);
    }

    /**
     * limit(long maxSize)
     * 截取Stream中的前 maxSize 个元素
     */
    @Test
    public void limit() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> limitedNumbers = numbers.stream()
                .limit(3)   // 截取前3个数字 [1, 2, 3]
                .collect(Collectors.toList());
        System.out.println(limitedNumbers);
    }

    /**
     * skip(long n)
     * 跳过Stream中的前n个元素
     */
    @Test
    public void skip() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> skippedNumbers = numbers.stream()
                .skip(2)    // 跳过前2个数字 [3, 4, 5]
                .collect(Collectors.toList());
        System.out.println(skippedNumbers);
    }

    /**
     * collect(Collector<T, A, R> collector)
     * 将Stream中的元素收集到一个集合或数据结构中，并返回结果
     */
    @Test
    public void collect() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        Stream<String> stringStream = Stream.of("John", "Alice", "Bob");
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        Stream<Double> doubleStream = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0);

        // toList()：将流中的元素收集到一个 List 中
        List<Integer> intList1 = numbers.stream().collect(Collectors.toList());
        List<Integer> intList2 = new ArrayList<>(numbers);

        // toSet()：将流中的元素收集到一个 Set 中
        Set<Integer> collectSet1 = numbers.stream().collect(Collectors.toSet());
        Set<Integer> collectSet2 = new HashSet<>(numbers);

        // toMap()：将流中的元素根据指定的键和值的提取函数收集到一个 Map 中
        Map<String, Integer> ageMap = stringStream.collect(Collectors.toMap(Function.identity(), String::length));

        // joining()：将流中的元素拼接成一个字符串
        String result = stringStream.collect(Collectors.joining(", "));

        // groupingBy()：根据指定的分类函数对流中的元素进行分组
        Map<Character, List<String>> groupedNames = stringStream.collect(Collectors.groupingBy(s -> s.charAt(0)));

        // partitioningBy()：根据指定的条件对流中的元素进行分区
        Map<Boolean, List<String>> partitionedNames = stringStream.collect(Collectors.partitioningBy(s -> s.length() > 3));

        // mapping()：对流中的元素进行映射，并将结果收集到一个集合中
        List<Integer> lengths1 = stringStream.collect(Collectors.mapping(String::length, Collectors.toList()));
        List<Integer> lengths2 = stringStream.map(String::length).collect(Collectors.toList());

        // reducing()：根据指定的规约操作对流中的元素进行规约
        Optional<Integer> reducing1 = integerStream.collect(Collectors.reducing(Integer::sum));
        Optional<Integer> reducing2 = integerStream.reduce(Integer::sum);

        // collectingAndThen()：对流中的元素进行收集，并执行一个最终的转换操作
        String longestName = stringStream.collect(Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(String::length)), Optional::get));

        // toCollection()：将流中的元素收集到指定的集合类型中
        LinkedList<String> names = stringStream.collect(Collectors.toCollection(LinkedList::new));

        // groupingByConcurrent()：并发地根据指定的分类函数对流中的元素进行分组
        ConcurrentMap<Character, List<String>> concurrentGroupedNames = stringStream.collect(Collectors.groupingByConcurrent(s -> s.charAt(0)));

        // toConcurrentMap()：将流中的元素根据指定的键和值的提取函数收集到一个并发的 Map 中
        ConcurrentMap<String, Integer> concurrentAgeMap = stringStream.collect(Collectors.toConcurrentMap(Function.identity(), String::length));


        // counting()：对流中的元素进行计数
        long count1 = stringStream.collect(Collectors.counting());
        long count2 = stringStream.count();

        // summingInt()：对流中的元素进行求和，返回一个整数
        int sum1 = integerStream.collect(Collectors.summingInt(Integer::intValue));
        int sum2 = integerStream.mapToInt(Integer::intValue).sum();

        // averagingInt()：对流中的元素进行求平均值，返回一个 double 类型
        double average = integerStream.collect(Collectors.averagingInt(Integer::intValue));

        // maxBy()：根据指定的比较器选择流中的最大元素
        Optional<String> maxName1 = stringStream.collect(Collectors.maxBy(Comparator.naturalOrder()));
        Optional<String> maxName2 = stringStream.max(Comparator.naturalOrder());

        // minBy()：根据指定的比较器选择流中的最小元素
        Optional<String> minName1 = stringStream.collect(Collectors.minBy(Comparator.naturalOrder()));
        Optional<String> minName2 = stringStream.min(Comparator.naturalOrder());

        // summarizingInt()：对流中的元素进行统计，返回一个统计结果对象
        IntSummaryStatistics statistics = integerStream.collect(Collectors.summarizingInt(Integer::intValue));

        // summingDouble()：对流中的元素进行求和，返回一个 double 类型
        double summingDouble1 = doubleStream.collect(Collectors.summingDouble(Double::doubleValue));
        double summingDouble2 = doubleStream.mapToDouble(Double::doubleValue).sum();
    }

    /**
     * toArray()
     * 将Stream中的元素转换为数组
     */
    @Test
    public void toArray() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        Integer[] numberArray = numbers.stream().toArray(Integer[]::new);   // 转换为数组 [1, 2, 3, 4, 5]
        Integer[] integers = numbers.toArray(new Integer[0]);   // 上面 toArray() 的简写
        System.out.println(JSON.toJSON(numberArray));
        System.out.println(JSON.toJSON(integers));
    }

    /**
     * anyMatch(Predicate<T> predicate)
     * 判断Stream中是否存在满足给定条件的元素
     */
    @Test
    public void anyMatch() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        boolean hasEvenNumber = numbers.stream().anyMatch(num -> num % 2 == 0); // 是否有偶数，结果为true
        System.out.println(hasEvenNumber);
    }

    /**
     * allMatch(Predicate<T> predicate)
     * 判断Stream中的所有元素是否都满足给定条件
     */
    @Test
    public void allMatch() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        boolean allEvenNumbers = numbers.stream().allMatch(num -> num % 2 == 0); // 是否都是偶数，结果为false
        System.out.println(allEvenNumbers);
    }

    /**
     * noneMatch(Predicate<T> predicate)
     * 判断Stream中是否不存在满足给定条件的元素
     */
    @Test
    public void noneMatch() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        boolean noEvenNumber = numbers.stream().noneMatch(num -> num % 2 == 0); // 是否没有偶数，结果为false
        System.out.println(noEvenNumber);
    }

    /**
     * findFirst()
     * 返回Stream中的第一个元素
     */
    @Test
    public void findFirst() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
        Optional<Integer> anyNumber = numbers.stream().findAny();   // 第一个数字，结果为Optional[1]
        System.out.println(anyNumber);
    }

    /**
     * findAny()
     * 返回Stream中的任意一个元素
     */
    @Test
    public void findAny() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> anyNumber = numbers.stream().findAny();   // 任意一个数字，结果为Optional[1]或其他数字
        System.out.println(anyNumber);
    }

    /**
     * reduce()
     * 是把一个流缩减成一个值，能实现对集合求和、求乘积和求最值操作（缩减操作）
     */
    public void reduce() {
        List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
        // 求和方式1
        Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
        // 求和方式2
        Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
        // 求和方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);

        // 求乘积
        Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

        // 求最大值方式1
        Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值写法2
        Integer max2 = list.stream().reduce(1, Integer::max);

        System.out.println("list求和：" + sum.get() + "," + sum2.get() + "," + sum3);
        System.out.println("list求积：" + product.get());
        System.out.println("list求最大值：" + max.get() + "," + max2);
    }


    /**
     * 取集合中的某一字段作为Key，进行分组
     */
    @Test
    public void groupingBy() {
        Map<String, List<Student>> collect = list.stream().collect(Collectors.groupingBy(Student::getName));
        System.out.println(collect);
    }

    /**
     * 取对象中的某个元素并连接它们，用逗号分隔
     */
    @Test
    public void joining() {
        String joined = list.stream()
                .map(Student::getName)
                .collect(Collectors.joining(", "));
        System.out.println(joined);
    }

    /**
     * Collectors.partitioningBy 是一种将流的元素始终分为两部分的方法
     * 它返回一个在 Map 中存储值的收集器，在Map中的key只能是true或false
     */
    @Test
    public void partitioningBy() {
        Map<Boolean, List<Student>> stdByClass = list.stream()
                .collect(Collectors.partitioningBy(s -> s.getAge() > 30));
        System.out.println(stdByClass);
    }

    @Data
    @Builder
    static
    class Student {
        private String name;
        private Integer age;
        private String sex;
    }

}
